#include <iostream>
#include <vector>  
#include <string>
#include <unistd.h>
#include <pthread.h>

#define NUM 3

// 其实线程和线程之间，几乎没有秘密。
// 线程栈上的数据，也是可以被其他线程访问并看到。

// 主线程可以拿到任意一个新线程中的数据。
// int *p = NULL;

// 全局变量是可以被所有线程同时看到的。
// int g_val = 100;

// 线程如果想要一个私有的全局变量呢？
// 线程局部存储。一个变量，但是每个线程私有一份。
// __thread是编译器提供的编译选项。只能用来定义内置类型，自定义类型不允许。
__thread int g_val = 200;
// 线程局部存储有什么用呢？
// 我们在线程内部获取自己的tid时要使用pthread_self.
// 可以定义__thread int number = 0;
// 然后在线程内部就不频繁调用pthread_self了，而是将number = pthread_self()
// 或者想知道这个线程属于哪个进程
// __thread int pid;
// 直接在线程创建的时候获取pid = getpid()

// 线程
struct threadData
{
    std::string tid;
    std::string threadname;
};

void InitThreadData(threadData *td, int number, pthread_t pid)
{
    td->threadname = "thread-" + std::to_string(number); // thread-0

    char buffer[128];
    snprintf(buffer, sizeof(buffer), "0x%x", pid);
    td->tid = buffer;
}

std::string toHex(pthread_t tid)
{
    char buffer[1024];
    snprintf(buffer, sizeof(buffer), "0x%x", tid);
    return buffer;
}

void* threadRoutine(void* args)
{
    // printf("堆空间: %p\n", &args);
    int test_i = 0;
    threadData* td = static_cast<threadData*>(args);
    // if(td->threadname == "thread-2") p = &test_i;
    int i = 0;
    while(i++ < 10)
    {
        std::cout << "pid: " << getpid() << ", tid: "
            << td->tid << ", threadname: " << td->threadname 
            << ", g_val: " << g_val << ", &g_val: " << &g_val << std::endl;
        test_i++;
        g_val++;
        sleep(1); 
    }
    delete td;
    return nullptr;
}

int main()
{
    std::vector<pthread_t> tids;
    for(int i = 0; i < NUM; ++i)
    {
        // 这样写是否可以？
        // 不可以。因为td变量是属于for循环的代码块，更是main函数的内部，因此td变量是在主线程的栈上。
        // for循环执行完毕后，这个td变量也会被销毁。而给新线程传递的参数是一个指针，让新线程指向
        // 主线程的栈，还是个临时变量。这样会有很大问题
        /*threadData td;
        td.threadname = ???;
        td.tid = ?;*/

        // 这样写就可以?为什么。因为此时td虽然还是局部变量，但args是td的拷贝，args指的是堆空间的内容。
        // 而且每次for循环都会创建一个堆空间。因此每个线程以后都可以访问堆空间。但是他们访问的都是堆空间不同的区域。
        // 虽然for循环结束td被释放了，但是线程函数中都会有自己管理的堆空间。
        pthread_t pid;
        threadData *td = new threadData;
        // printf("栈空间: %p\n", &td);
        InitThreadData(td, i, pid);
        // 10个新线程都会执行threadRoutine这个线程。
        pthread_create(&pid, nullptr, threadRoutine, td);

        // 保存创建好的线程
        tids.push_back(pid);
        sleep(1);
    }
    sleep(1);
    // std::cout << "main thread get a value: " << *p << ", &val: " << p << std::endl;
    // 主线程等待，不保证谁先运行，但主线程一定最后退出
    for(int i = 0; i < NUM; ++i)
    {
        pthread_join(tids[i], nullptr);
    }

    // 线程分离
    // 默认情况下，新创建的线程是joinable的，线程退出后，需要对其进行pthread_join操作，否则无法释放资源，从而造成内存泄漏。
    // 如果不关心线程的返回值，join是一种负担，这个时候就可以告诉系统，当线程退出时，自动释放线程资源。
    // int pthread_detach(pthread_t tid)，可以理解为非阻塞等待。
    // 可以是线程组内其他线程对目标线程进行分离，也可以是线程自己分离。
    return 0;
}